/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package br.ufrj.dcc.pushpeer.dynamics;

import java.util.Set;

import br.ufrj.dcc.pushpeer.dht.SimpleDHT;
import br.ufrj.dcc.pushpeer.events.Event;
import br.ufrj.dcc.pushpeer.events.EventType;
import br.ufrj.dcc.pushpeer.initialization.NodeInitializer;
import br.ufrj.dcc.pushpeer.protocol.PushPeer;
import peersim.config.Configuration;
import peersim.core.CommonState;
import peersim.core.Control;
import peersim.core.Network;
import peersim.core.Node;
import peersim.edsim.EDSimulator;
import peersim.transport.Transport;

public class NetworkDynamics implements Control {
	
	/**
	 *	The protocol to operate on.
	 */
	private static final String PAR_PROT="protocol";
	
	/**
	 * The Transport used by the the control.
	 */
	private static final String PAR_TRANSPORT="transport";

	/**
	 * The number of files being simulated.
	 */
	private static final String NUM_FILES="num_files";
	
	private final int pid;
	private final int tid;
	private final int numFiles;
	
	private final int AVG_CHANGING_NODES = 3;
	private final int STD_DEV_CHANGING_NODES = 2;
	
	private final NodeInitializer init;
	
	/**
	 * Standard constructor that reads the configuration parameters.
	 * Invoked by the simulation engine.
	 * @param prefix the configuration prefix for this class
	 */
	public NetworkDynamics(String prefix) {
		pid = Configuration.getPid(prefix + "." + PAR_PROT);
		tid = Configuration.getPid(prefix + "." + PAR_TRANSPORT);
		numFiles = Configuration.getInt(prefix + "." + NUM_FILES); 
		
		init = new NodeInitializer("init.net");
	}
	
	
	/**
	 * Adds n nodes to the network.
	 * @param n The number of nodes to add, must be non-negative.
	 */
	protected void add(int n) {
		if (n==0)
			return;
		for (int i = 0; i < n; ++i) {
			//Create a new node.
			Node nodeToBeAdded = (Node) Network.prototype.clone();
			
			//Add the new node to the network.
			Network.add(nodeToBeAdded);
			
			//Adds to the DHT.
			SimpleDHT.insertNode(nodeToBeAdded);
			
			/*
			 * Initialize the new node using the NodeInitializer class;
			 * this it the same as init.initialize(Network.get(Network.size()-1));
			 */
			init.initialize(nodeToBeAdded);

			Node cb = ((PushPeer)nodeToBeAdded.getProtocol(pid)).getCB();
			long latency = ((Transport)nodeToBeAdded.getProtocol(tid)).getLatency(nodeToBeAdded, cb);
			
			Object ev;
			ev = new Event(EventType.REGISTER_IN_CB, nodeToBeAdded, latency);
			EDSimulator.add(latency, ev, cb, pid);
			
			//Kickstarts the file piece requests.
			PushPeer p = (PushPeer)nodeToBeAdded.getProtocol(pid);
			for(int j=1; j <= numFiles; j++)
				if(p.getFileWantedStatus(j) == PushPeer.WANTED)
					p.nextRequest(nodeToBeAdded, pid, j);
		}
	}
	
	/**
	 * Removes n nodes from the network.
	 * @param n The number of nodes to remove.
	 */
	protected void remove(int n) {
		//The index of the node to be removed.
		int nodeIndex=0;
		
		for (int i=0; i<n; ++i) {
			nodeIndex = CommonState.r.nextInt(Network.size());

			//Removes node from the DHT.
			SimpleDHT.removeNode(Network.get(nodeIndex));
			
			//Remove this node from it's CB's managed peer set.
			Node cb = ((PushPeer)Network.get(nodeIndex).getProtocol(pid)).getCB();
			PushPeer p = (PushPeer)cb.getProtocol(pid); 
			p.unsetManagedPeer(Network.get(nodeIndex));
			
			PushPeer removedNode = (PushPeer) Network.get(nodeIndex).getProtocol(pid);
			Set<Node> managedPeers = removedNode.getManagedPeers();
			
			//Remove the node with the given index from the network.
			Network.remove(nodeIndex);
			
			//Alert the managed peers that it's leaving.
			for(Node mp : managedPeers) {
				PushPeer mppp = (PushPeer)mp.getProtocol(pid);
				mppp.resetCB(mp, pid);
			}
		}
	}
		
	/**
	 * Adds and removes new nodes to/from the network.
	 * @return Always false. 
	 */
	public boolean execute() {
		int numToRemove = (int)(Math.abs(CommonState.r.nextGaussian())*STD_DEV_CHANGING_NODES + AVG_CHANGING_NODES);
		remove(numToRemove);
		
		int numToAdd = (int)(Math.abs(CommonState.r.nextGaussian())*STD_DEV_CHANGING_NODES + AVG_CHANGING_NODES);
		add(numToAdd);
		
		return false;
	}
}